package com.ccp.dev.system.controller;

import com.ccp.dev.core.basic.annotion.Action;
import com.ccp.dev.core.basic.consts.SystemConst;
import com.ccp.dev.core.basic.internationalization.LocaleMessageSource;
import com.ccp.dev.core.basic.internationalization.SysBaseController;
import com.ccp.dev.core.basic.model.SysAuditModelType;
import com.ccp.dev.core.basic.response.ResultData;
import com.ccp.dev.core.basic.util.ContextUtil;
import com.ccp.dev.core.basic.util.PropertyUtil;
import com.ccp.dev.core.basic.util.UUIDUtils;
import com.ccp.dev.core.util.BeanUtils;
import com.ccp.dev.core.util.RequestUtil;
import com.ccp.dev.core.util.StringUtil;
import com.ccp.dev.core.util.log.SysAuditThreadLocalHolder;
import com.ccp.dev.system.configuration.CustomPwdEncoder;
import com.ccp.dev.system.license.SysLicense;
import com.ccp.dev.system.model.*;
import com.ccp.dev.system.service.*;
import com.ccp.dev.system.util.RandomValidateCodeUtil;
import com.ccp.dev.system.util.SecurityUtil;
import com.ccp.dev.system.util.SubSystemUtil;
import com.ccp.dev.system.util.SysRoleUtil;
import net.sf.json.JSONObject;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.unit.DataSize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

import javax.annotation.Resource;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;

/**
 * 登录控制器
 *
 * @author y
 */
@Controller
@Action(ownermodel = SysAuditModelType.LOGIN_MANAGEMENT)
public class LoginController extends SysBaseController {
    @Resource
    private SysUserService sysUserService;
    @Resource
    private SubSystemService subSystemService;
    @Resource
    private SessionAuthenticationStrategy sessionStrategy;
    @Resource
    private PwdStrategyService pwdStrategyService;
    @Resource
    private SysRoleService sysRoleService;
    @Resource
    private SysLoginLogService sysLoginLogService;
    @Resource
    protected LocaleMessageSource localeMessageSource;
    @Resource
    private SysOrgService sysOrgService;
    @Resource
    private SysPropertyService sysPropertyService;

    @Value("${login.missNumberLock}")
    private Short missNumberLock = 5;

    @Value("${login.missNumberUseValidCode}")
    private Short missNumberUseValidCode = 3;

    @Value("${login.useValidCode}")
    private Boolean useValidCode = true;

    @Value("${rememberMe.tokenValiditySeconds}")
    private Long tokenValiditySeconds = 1209600L;

    @Value("${rememberMe.name}")
    private String rememberMeName;

    @Value("${rememberMe.secret}")
    private String rememberMeSecret;

    @Value("${spring.servlet.multipart.max-file-size}")
    private DataSize multipartMaxSize;

    @Value("${top.org.name}")
    private String topOrgName;

    @Value("${server.imModel.load}")
    private Boolean loadIm = false;


    /**
     * 访问登录页面
     *
     * @param request HttpServletRequest
     * @return modelAndView
     */
    @RequestMapping({"/login"})
    public ModelAndView login(HttpServletRequest request) {
        SecurityContextHolder.clearContext();
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("ctx", request.getContextPath());
        modelAndView.addObject("useValidCode", useValidCode);
        modelAndView.setViewName("login");
        return modelAndView;
    }

    /**
     * 执行登录逻辑
     *
     * @param request  HttpServletRequest
     * @param response HttpServletResponse
     * @return modelAndView
     */
    @Action(description = "用户登录",  detail = "<#if success><#assign entity=sysUserService.getByAccount(account)/>用户【${entity.fullName}】登录系统成功<#else>账号【${account}】登录失败</#if>",exectype = "登录日志")
    @PostMapping("/loginExecute")
    @ResponseBody
    public ResultData loginExecute(HttpServletRequest request, HttpServletResponse response) {
        SecurityContextHolder.clearContext();
        String account = RequestUtil.getString(request, "account");
        String password = RequestUtil.getString(request, "password");
        Boolean toManyPassErr = RequestUtil.getBoolean(request, "useValidCode");
        SysLoginLog loginLog = new SysLoginLog();
        loginLog.setId(UUIDUtils.getUUIDFor32());
        loginLog.setAccount(account);
        loginLog.setLogintime(new Date());
        loginLog.setIp(RequestUtil.getIpAddr(request));
        loginLog.setTenantid(ContextUtil.getCurrentUserTenantId());
        //默认登录成功
        loginLog.setStatus(SysLoginLog.SUCCESS);
        loginLog.setNote("登录成功");
        SysAuditThreadLocalHolder.putParamerter("success", true);
        SysAuditThreadLocalHolder.putParamerter("account", account);
        try {
            //需要验证码，验证验证码
            if (useValidCode || toManyPassErr) {
                String validCode = (String) request.getSession().getAttribute(RandomValidateCodeUtil.RANDOM_CODE_KEY);
                String code = request.getParameter("validCode");
                if (StringUtil.isEmpty(validCode) || StringUtil.isEmpty(code) || !validCode.equalsIgnoreCase(code)) {
                    //验证码错误
                    String messageKey = "login.validCode.warning";
                    loginLog.setStatus(SysLoginLog.VCODE_ERR);
                    loginLog.setNote(localeMessageSource.getMessage(messageKey));
                    sysLoginLogService.add(loginLog);
                    SysAuditThreadLocalHolder.putParamerter("success", false);
                    return getResultDataError(messageKey);
                }
            }
            if (StringUtil.isEmpty(account) || StringUtil.isEmpty(password)) {
                //用户名或密码为空
                String messageKey = "login.certificate.warning";
                loginLog.setStatus(SysLoginLog.ACCOUNT_PWD_EMPTY);
                loginLog.setNote(localeMessageSource.getMessage(messageKey));
                sysLoginLogService.add(loginLog);
                SysAuditThreadLocalHolder.putParamerter("success", false);
                return getResultDataError(messageKey);
            }
            List<SysUser> userList = sysUserService.queryByAccount(account);
            if(BeanUtils.isEmpty(userList)){
                //账号不存在
                String messageKey = "login.username.warning";
                loginLog.setStatus(SysLoginLog.ACCOUNT_PWD_ERR);
                loginLog.setNote(localeMessageSource.getMessage(messageKey));
                sysLoginLogService.add(loginLog);
                SysAuditThreadLocalHolder.putParamerter("success", false);
                return getResultDataError(messageKey);
            }
            SysUser sysUser;
            if(userList.size()==1){
                sysUser = userList.get(0);
            }else{
                String userId = RequestUtil.getString(request, "userId");
                if(StringUtil.isNotEmpty(userId)){
                    sysUser = sysUserService.getOneById(userId);
                }else{
                    return new ResultData(2, "login.choose.user", userList.size(), userList);
                }
            }
            if (!SysUser.STATUS_OK.equals(sysUser.getStatus())) {
                //账号未激活
                String messageKey = "login.username.notActive.warning";
                loginLog.setStatus(SysLoginLog.ACCOUNT_DISABLED);
                loginLog.setNote(localeMessageSource.getMessage(messageKey));
                //用户登录日志补充姓名、组织信息
                LoginFailureSetName(loginLog,sysUser.getFullName(),sysUser.getUserId());
                sysLoginLogService.add(loginLog);
                SysAuditThreadLocalHolder.putParamerter("success", false);
                return getResultDataError(messageKey);
            }
            if (SysUser.LOCKED.equals(sysUser.getIsLock())) {
                //账号锁定
                String messageKey = "login.username.locked.warning";
                loginLog.setStatus(SysLoginLog.ACCOUNT_LOCKED);
                loginLog.setNote(localeMessageSource.getMessage(messageKey));
                //用户登录日志补充姓名、组织信息
                LoginFailureSetName(loginLog,sysUser.getFullName(),sysUser.getUserId());
                sysLoginLogService.add(loginLog);
                SysAuditThreadLocalHolder.putParamerter("success", false);
                return getResultDataError(messageKey);
            }
            if (SysUser.EXPIRED.equals(sysUser.getIsExpired())) {
                //账号过期
                String messageKey = "login.username.expired.warning";
                loginLog.setStatus(SysLoginLog.ACCOUNT_OVERDUE);
                loginLog.setNote(localeMessageSource.getMessage(messageKey));
                //用户登录日志补充姓名、组织信息
                LoginFailureSetName(loginLog,sysUser.getFullName(),sysUser.getUserId());
                sysLoginLogService.add(loginLog);
                SysAuditThreadLocalHolder.putParamerter("success", false);
                return getResultDataError(messageKey);
            }
            CustomPwdEncoder customPwdEncoder = new CustomPwdEncoder();
            String enPassword = customPwdEncoder.encode(password);
            String userId = sysUser.getUserId();
            Short number = sysUser.getMissNumber();
            //获取使用中的密码策略
            PwdStrategy pwdStrategy = pwdStrategyService.getUsing();
            if (!sysUser.getPassword().equals(enPassword)) {
                //错误多少次后锁定
                Short errLockAccount;
                //错误多少次后显示验证码
                Short verifyCodeAppear;
                //密码策略为空走默认设置
                if (pwdStrategy == null) {
                    errLockAccount = missNumberLock;
                    verifyCodeAppear = missNumberUseValidCode;
                } else {
                    errLockAccount = pwdStrategy.getErrLockAccount();
                    verifyCodeAppear = pwdStrategy.getVerifyCodeAppear();
                }
                number = (number == null || number == 0) ? 1 : ++number;
                if (number >= errLockAccount && errLockAccount != 0) {
                    sysUserService.updateIsLock(userId, SysUser.LOCKED);
                } else {
                    sysUserService.updateMissNumber(userId, number);
                }
                Map<String, Object> map = new HashMap<>(1);
                map.put("useValidCode", false);
                //后台不强制使用验证码时验证是否需要验证码
                if (!useValidCode && number >= verifyCodeAppear && verifyCodeAppear != 0) {
                    map.put("useValidCode", true);
                }

                String messageKey = "login.password.warning";
                loginLog.setStatus(SysLoginLog.ACCOUNT_PWD_ERR);
                loginLog.setNote(localeMessageSource.getMessage(messageKey));
                //用户登录日志补充姓名、组织信息
                LoginFailureSetName(loginLog,sysUser.getFullName(),userId);
                sysLoginLogService.add(loginLog);
                SysAuditThreadLocalHolder.putParamerter("success", false);
                return getResultDataError(messageKey, 0L, map);
            }
            //验证会话license
            boolean isValid = SysLicense.validateSessionLicnese(request);
            if(!isValid){
                String messageKey = "login.valid.license.warning";
                loginLog.setStatus(SysLoginLog.SESSION_LICENSE_ERR);
                loginLog.setNote(localeMessageSource.getMessage(messageKey));
                sysLoginLogService.add(loginLog);
                SysAuditThreadLocalHolder.putParamerter("success", false);
                return getResultDataError(messageKey);
            }
            //登录
            Authentication authentication = SecurityUtil.login(request, sysUser.getUserId(), password, false);
            //session认证
            sessionStrategy.onAuthentication(authentication, request, response);
            //更新状态
            this.sysUserService.loginSuccessSet(userId, RequestUtil.getIpAddr(request));
            //写出cookie
            writeRememberMeCookie(request, response, account, enPassword);
            //登录成功
            loginLog.setUserName(ContextUtil.getCurrentUser().getFullName());
            if(StringUtil.isNotEmpty(userId) && !SystemConst.SUPER_ADMIN_ID.equals(userId)){
                if(sysOrgService.getOrgByUserId(userId).getOrgPathname().indexOf("/"+topOrgName) != -1){
                    loginLog.setOrgName(ContextUtil.getCurrentOrg().getOrgPathname().split("/"+topOrgName)[1]);
                }else{
                    loginLog.setOrgName(ContextUtil.getCurrentOrg().getOrgPathname());
                }
            }
            sysLoginLogService.add(loginLog);
            return getResultDataSuccess("login.success");
        } catch (Exception e) {
            //登录失败
            SecurityContextHolder.clearContext();
            String messageKey = "login.fail";
            loginLog.setStatus(SysLoginLog.ERROR);
            loginLog.setNote(e.getMessage());
            SysAuditThreadLocalHolder.putParamerter("success", false);
            return getResultDataError(messageKey);
        }
    }

    /**
     * 用户登录日志信息补充登录人姓名userName、登录人组织orgName（用户登录失败时）
     * @param loginLog 用户登录日志信息
     * @param userName 登录人姓名
     * @param userId   登录人组织
     */
    private void LoginFailureSetName(SysLoginLog loginLog, String userName, String userId) {
        loginLog.setUserName(userName);
        //赋值组织信息
        if(StringUtil.isNotEmpty(userId) && !SystemConst.SUPER_ADMIN_ID.equals(userId)){
            SysOrg sysOrg = sysOrgService.getOrgByUserId(userId);
            if(sysOrg.getOrgPathname().indexOf("/"+topOrgName) != -1){
                loginLog.setOrgName(sysOrg.getOrgPathname().split("/"+topOrgName)[1]);
            }else{
                loginLog.setOrgName(sysOrg.getOrgPathname());
            }
        }
    }


    /**
     * 加上用户登录的remember me 的cookie
     *
     * @param request    HttpServletRequest
     * @param response   HttpServletResponse
     * @param username   用户名
     * @param enPassword 密码
     */
    private void writeRememberMeCookie(HttpServletRequest request, HttpServletResponse response, String username, String enPassword) {
        //记住密码
        final String rememberPasswordYes = "on";
        //获取标记
        String rememberMe = request.getParameter("_spring_security_remember_me");
        if (rememberPasswordYes.equals(rememberMe)) {
            // 14 days
            long tokenExpiryTime = System.currentTimeMillis() + (tokenValiditySeconds * 1000);
            String signatureValue = DigestUtils.md5Hex(username + ":" + tokenExpiryTime + ":" + enPassword + ":" + rememberMeSecret);
            String tokenValue = username + ":" + tokenExpiryTime + ":" + signatureValue;
            String tokenValueBase64 = new String(Base64.encodeBase64(tokenValue.getBytes()));
            Cookie cookie = new Cookie(rememberMeName, tokenValueBase64);
            // 5 years
            cookie.setMaxAge(60 * 60 * 24 * 365 * 5);
            cookie.setPath(StringUtil.isNotEmpty(request.getContextPath()) ? request.getContextPath() : "/");
            response.addCookie(cookie);
        }
    }

    /**
     * 保存选择的系统和角色
     *
     * @return ResultData
     */
    @Action(description = "保存选择的系统和角色",  detail = "保存选择的系统和角色<#if success>成功<#else>失败</#if>",exectype = "登录日志")
    @ResponseBody
    @PostMapping("/saveCurrentSysAndRole")
    public ResultData saveCurrentSysAndRole(HttpServletRequest request, HttpServletResponse response) {
        try {
            String systemId = RequestUtil.getString(request, "systemId");
            String roleId = RequestUtil.getString(request, "roleId");
            if (StringUtil.isNotEmpty(systemId)) {
                subSystemService.setCurrentSystem(systemId, request, response);
            }
            if (StringUtil.isNotEmpty(roleId)) {
                sysRoleService.setCurrentRole(roleId, request, response);
            }
            SysAuditThreadLocalHolder.putParamerter("success", true);
            return getResultDataSuccess("operation.success");
        } catch (Exception e) {
            SysAuditThreadLocalHolder.putParamerter("success", false);
            return getResultDataError("operation.failure");
        }
    }

    @RequestMapping("/home")
    public String home() {
        return "home";
    }

    @RequestMapping(value = {"/homePage"})
    public String homePage() {
        return "homePage";
    }

    @RequestMapping("/index2")
    public String index2(HttpServletRequest request,Model model) {
        model.addAttribute("ctx",request.getContextPath());
        model.addAttribute("multipartMaxSize",String.valueOf(multipartMaxSize.toKilobytes()));
        return "index2";
    }

    @RequestMapping("/system/theme")
    public String theme() {
        return "system/theme";
    }

    @RequestMapping("/templates/system/theme")
    public String templateTheme() {
        return "system/theme";
    }

    /**
     * 加载公用的js、css
     *
     * @param request  HttpServletRequest
     * @param response HttpServletResponse
     * @param model    Model
     * @return String
     */
    @RequestMapping("/index")
    public String index(HttpServletRequest request, HttpServletResponse response, Model model) throws IOException {
        String switchUrl = "user/switchSysAndRoleView";
        String retUrl = "index";
        SysUser sysUser = (SysUser) ContextUtil.getCurrentUser();
        //当前子系统
        SubSystem currentSystem = SubSystemUtil.getCurrentSystem(request);
        //当前有权限的子系统
        List<SubSystem> subSystemList = subSystemService.getByUser(sysUser);
        model.addAttribute("subSystemList", subSystemList);
        if (currentSystem == null) {
            //session和cookie中没有记录当前子系统
            if (subSystemList != null && subSystemList.size() == 1) {
                //如果当前用户只有一个可访问的子系统
                currentSystem = subSystemList.get(0);
                subSystemService.setCurrentSystem(currentSystem.getSystemId(), request, response);
            } else {
                //当前用户可以访问多个子系统，并且之前没有选择过子系统
                retUrl = switchUrl;
            }
        } else {
            //session和cookie中记录了当前子系统
            if (!subSystemList.contains(currentSystem)) {
                //以前有选择系统,但是现在即不再拥用该系统的权限,重新选择系统
                retUrl = switchUrl;
            }
        }

        //当前角色
        SysRole currentSysRole = SysRoleUtil.getCurrentRole(request);
        List<SysRole> sysRoleList = new ArrayList<>();
        //当前角色
        if (currentSystem != null && !retUrl.equals(switchUrl)) {
            //查询当前子系统下所有可切换的角色
            sysRoleList = sysRoleService.querySwitchBySystemId(currentSystem.getSystemId());
            if (sysRoleList == null || sysRoleList.isEmpty()) {
                //如果没有可以切换的角色，那么就直接登录当前系统，并从session和cookie中删除当前角色
                SysRoleUtil.removeRole(request, response);
            } else if (sysRoleList.size() == 1) {
                //只有一个可以切换的角色，那么就直接登录当前系统，并将当前的角色设置为此角色
                currentSysRole = sysRoleList.get(0);
                sysRoleService.setCurrentRole(currentSysRole.getRoleId(), request, response);
            } else {
                if (!sysRoleList.contains(currentSysRole)) {
                    //以前有选择可切换的角色,但是现在不再拥有该角色，重新选择角色
                    retUrl = switchUrl;
                }
            }
        }
        //判断页面右上方是否需要显示切换角色
        String systemId = SubSystemUtil.getCurrentSystemId(request);
        if (StringUtil.isNotEmpty(systemId)) {
            String currRoleId = SysRoleUtil.getCurrentRoleId(request);
            if (!sysRoleList.isEmpty() && sysRoleList.size() > 1) {
                model.addAttribute("showSwitchSelect", true);
                model.addAttribute("sysRoleList", sysRoleList);
                model.addAttribute("currRoleId", currRoleId);
            } else {
                model.addAttribute("showSwitchSelect", false);
            }
        }

        //获取系统属性中的平台名称
        String sysPropertyValue = sysPropertyService.getByAlias("appName");

        //判断密码策略是否合格
        PwdStrategy usePwdStrategy = pwdStrategyService.getUsing();
        if (null != usePwdStrategy) {
            JSONObject jsonObject = pwdStrategyService.checkUser(sysUser, null);
            int status = jsonObject.getInt("status");
            String msg = jsonObject.getString("msg");
            if (status != PwdStrategy.Status.SUCCESS) {
                model.addAttribute("showRestPwd", true);
                model.addAttribute("msg", msg);
            }
        }

        model.addAttribute("ctx",request.getContextPath());
        model.addAttribute("multipartMaxSize",String.valueOf(multipartMaxSize.toKilobytes()));
        model.addAttribute("sysPropertyValue",sysPropertyValue);
        model.addAttribute("systemSec", PropertyUtil.getByAlias("systemSec"));// 系统密级

        if("index".equals(retUrl) && loadIm){
            model.addAttribute("loadIm",true);
        }
        return retUrl;
    }

    @RequestMapping("/error/{errorCode}")
    public ModelAndView getErrorView(@PathVariable("errorCode") int errorCode){
        return new ModelAndView(this.getAutoView());
    }
}
